# -*- coding: utf-8 -*-
import json
import logging
import re
from cStringIO import StringIO

from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.utils.encoding import smart_unicode
from django.views.decorators.http import require_POST, require_GET
from django.views.generic import TemplateView

from api.account.handler import (send_auth_code, check_auth_code,
                                 get_fixed_num, get_personal_info, delete_auth_code,
                                 add_fresh_coupon, add_new_fresh_coupon, check_black_ip_equip)

from api.preset.handler import get_fixed_bucket
from common.account.db import account as db
from common.account.db.account import set_financial_account, exists_username,get_financial_type_account_by_num
from common.account.model.account import ACCOUNT_STATUS, FinancialAccountType
from common.cache import account as cache
from common.utils import exceptions as err
from common.utils import tracker
from common.utils.api import (token_required, get_client_ip,
                              get_city, check_params, parse_p)
from common.utils.code import create_validate_code
from common.utils.decorator import response_wrapper
from common.utils.limit import check_user_name_chars, validate_username
from common.utils.respcode import StatusCode
from common.black_account.db import list_type_account
from common.black_account.model import WITHDRAW_BLACK_TYPE
from common.utils.limit import frequency_limit_v2
from common.prize.handler import check_bind

_LOGGER = logging.getLogger(__name__)

_VERSION = 25  # for 28 彩金, 老版本18彩金


@require_POST
@response_wrapper
def login(req):
    """ user login
        required: phone, password

        response:
            {
                'user_id': 'xxxx',
                'token': 'xxxx'
            }
    """
    query_dct = json.loads(smart_unicode(req.body))
    check_params(query_dct, ['user_name', 'password'])
    pparam = parse_p((req.GET.get('p')))
    client_ip = get_client_ip(req)
    check_black_ip_equip('IP', client_ip)
    client_city = get_city(client_ip)
    extend = {
        'ip': client_ip,
        'addr': client_city
    }
    user = db.login_account_with_phone(query_dct['user_name'],
                                       query_dct['password'],
                                       pparam.get('chn', ''),
                                       pparam.get('cvc', ''),
                                       extend=extend)
    tracker.track_login(user['id'], True)
    return get_personal_info(user)


@require_POST
@response_wrapper
def login_by_username(req):
    query_dct = json.loads(smart_unicode(req.body))
    check_params(query_dct, ['user_name', 'password'])
    pparam = parse_p((req.GET.get('p')))
    client_ip = get_client_ip(req)
    check_black_ip_equip('IP', client_ip)
    client_city = get_city(client_ip)
    extend = {
        'ip': client_ip,
        'addr': client_city
    }
    user = db.login_by_username(query_dct['user_name'],
                                query_dct['password'],
                                pparam.get('chn', ''),
                                pparam.get('cvc', ''),
                                extend=extend)
    tracker.track_login(user['id'], True)
    return get_personal_info(user)


@require_POST
@response_wrapper
def login_by_phone(req):
    query_dct = json.loads(smart_unicode(req.body))
    check_params(query_dct, ['phone', 'password'])
    pparam = parse_p((req.GET.get('p')))
    client_ip = get_client_ip(req)
    check_black_ip_equip('IP', client_ip)
    aid = pparam.get('aid', ''),
    check_black_ip_equip('equip', aid)
    client_city = get_city(client_ip)
    extend = {
        'ip': client_ip,
        'addr': client_city
    }
    user = db.login_by_phone(query_dct['phone'],
                             query_dct['password'],
                             pparam.get('chn', ''),
                             pparam.get('cvc', ''),
                             extend=extend)
    tracker.track_login(user['id'], True)
    return get_personal_info(user)


@require_POST
@response_wrapper
@token_required
def logout(req):
    """ user logout
        required: user login

        response:
            {}
    """
    db.logout_device(req.user_id, req.user.token)
    return {}


class ImageCodeView(TemplateView):
    def get(self, req):
        phone = req.GET.get('phone')
        if not phone:
            raise err.ParamError('invalid')
        image, code_str = create_validate_code()
        output = StringIO()
        image.save(output, 'PNG', quality=95)
        _LOGGER.info('create image code, phone:%s, code str:%s',
                     phone, code_str)
        cache.set_image_code(phone, code_str)
        return HttpResponse(output.getvalue(), 'image/png')

    @method_decorator(response_wrapper)
    def post(self, req):
        param_dct = json.loads(req.body)
        code = param_dct.get('code')
        phone = param_dct.get('phone')
        if not code or not phone:
            _LOGGER.warn('check image, code: %s, phone:%s', code, phone)
            raise err.ParamError('invalid')
        right_code = cache.get_image_code(phone)
        if not right_code or code.lower() != right_code.lower():
            _LOGGER.warn('check image, phone:%s, code:%s, right_code:%s', phone, code, right_code)
            # meanwhile, clear the code
            cache.clear_image_code(phone)
            raise err.ParamError('code invalid')
        # do send sms auth code
        phone = get_fixed_num(phone)
        ip = get_client_ip(req)
        check_black_ip_equip('IP', ip)
        code = send_auth_code(phone, ip=ip, need_check=False)
        return {}

    def dispatch(self, *args, **kwargs):
        return super(ImageCodeView, self).dispatch(*args, **kwargs)


class AuthCodeView(TemplateView):
    @frequency_limit_v2(code='get_auth_code')
    def get(self, req):
        """ send auth_code sms to client.
            required: phone num

            response: {}
        """
        query_dct = req.GET.dict()
        tracks = parse_p(req.GET.get('p'))
        check_params(query_dct, ('phone',), {'use': ''})
        phone = get_fixed_num(query_dct['phone'])
        exists = db.exists_phone(phone)
        use = query_dct['use']
        if use == 'resetpwd' and not exists:
            raise err.DataError(status=StatusCode.INVALID_USER)
        if use == 'resettradepwd' and not exists:
            raise err.DataError(status=StatusCode.INVALID_USER)
        if use == 'bindphone' and exists:
            raise err.DataError(status=StatusCode.DUPLICATE_PHONE)
        ip = get_client_ip(req)
        check_black_ip_equip('IP', ip)
        if use == 'resetpwd' or tracks:
            return {} if send_auth_code(phone, ip=ip) else {'image': True}
        else:
            return {'image': True}

    def post(self, req):
        """ check client input auth code
            required: phone, auth_code

            response: {} or error
        """
        query_dct = json.loads(req.body)
        check_params(query_dct, ('phone', 'code'))
        query_dct['phone'] = get_fixed_num(query_dct['phone'])
        is_right = check_auth_code(query_dct['phone'], query_dct['code'])
        if is_right:
            delete_auth_code(query_dct['phone'])
            return {}
        else:
            raise err.AuthenticateError(status=StatusCode.WRONG_AUTH_CODE)

    @method_decorator(response_wrapper)
    def dispatch(self, *args, **kwargs):
        return super(AuthCodeView, self).dispatch(*args, **kwargs)


class UserSettingsView(TemplateView):

    @method_decorator(token_required)
    def get(self, req, *args, **kwargs):
        """get user settings
        """
        user = req.user
        user_settings = db.get_user_settings(user)
        return user_settings

    @method_decorator(token_required)
    def post(self, req):
        """change user settings
        """
        user_settings = json.loads(req.body)
        user = req.user
        db.update_account_settings(user, user_settings)
        return user_settings

    @method_decorator(response_wrapper)
    def dispatch(self, *args, **kwargs):
        return super(UserSettingsView, self).dispatch(*args, **kwargs)


class UserView(TemplateView):
    """register(post), list(get), and update(put) user info"""

    @method_decorator(token_required)
    def get(self, req, *args, **kwargs):
        """get user info.
        """
        return get_personal_info(req.user.as_dict())

    def post(self, req):
        """ register new user.
        """
        params = json.loads(smart_unicode(req.body))
        tracks = parse_p(req.GET.get('p'))
        check_params(params, ['password'])
        if 'user_name' in params:
            params['user_name'] = validate_username(params['user_name'])
            # check nick name length
            if len(params['user_name']) < 2:
                raise err.ParamError(u'用户名太短')
            if not check_user_name_chars(params['user_name']):
                raise err.ParamError(u'用户名只能使用数字字母汉字')
            if exists_username(params['user_name']):
                raise err.AuthenticateError(status=StatusCode.DUPLICATE_USERNAME)

            if params.get('phone'):
                del params['phone']
        elif 'phone' in params:
            check_params(params, ['auth_code'])
            params['phone'] = get_fixed_num(params['phone'])
            if not check_auth_code(params['phone'], params['auth_code']):
                raise err.AuthenticateError(status=StatusCode.WRONG_AUTH_CODE)
            if db.exists_phone(params['phone']):
                raise err.AuthenticateError(status=StatusCode.DUPLICATE_PHONE)
            type_accounts = list_type_account('black_phone') or list_type_account('bad_phone')
            if params['phone'] in type_accounts:
                raise err.AuthenticateError(status=StatusCode.WITHDRAW_BLACK_PHONE)
        params['channel'] = tracks.get('chn', '')
        ip = get_client_ip(req)
        check_black_ip_equip('IP', ip)
        aid = tracks.get('aid', '')
        check_black_ip_equip('equip', aid)
        account = db.create_account(params, extend={
            'aid': tracks.get('aid', ''),
            'ip': ip,
            'addr': get_city(ip)
        })
        tracker.track_register(account.id, tracks.get('aid', ''), tracks.get('chn', ''))
        user = db.login_account(account.user_name, None,
                                tracks.get('chn', ''),
                                tracks.get('cvc', ''),
                                {'ip': ip, 'addr': get_city(ip)}, login_account=True)
        # add fresh award
        cvc = tracks.get('cvc', 0)
        if int(cvc) <= _VERSION:
            add_fresh_coupon(account.id)
        return get_personal_info(user)

    @method_decorator(token_required)
    def put(self, req):
        params = json.loads(req.body)
        user = req.user
        valid_params = {}

        for k in ('password', 'phone', 'avatar', 'trade_pwd', 'sign',
                  'citizen_id', 'name', 'user_name'):
            if k in params:
                valid_params[k] = params[k]
        if 'user_name' in valid_params:
            raise err.ParamError(u'用户名不能修改')

        if 'phone' in valid_params:
            check_params(params, ['auth_code'])
            valid_params['phone'] = get_fixed_num(params['phone'])
            if not check_auth_code(valid_params['phone'], params['auth_code']):
                raise err.AuthenticateError(status=StatusCode.WRONG_AUTH_CODE)
            if db.exists_phone(valid_params['phone']):
                raise err.AuthenticateError(status=StatusCode.DUPLICATE_PHONE)
            type_accounts = list_type_account('black_phone') or list_type_account('bad_phone')
            if params['phone'] in type_accounts:
                raise err.AuthenticateError(status=StatusCode.WITHDRAW_BLACK_PHONE)

        if 'password' in valid_params:
            check_params(params, ('old_password',))
            if len(valid_params['password']) < 6:
                raise err.ParamError('password too short')
            valid_params['password'] = db.encode_password(
                valid_params['password'])
            if db.encode_password(params['old_password']) != user.password:
                raise err.AuthenticateError(status=StatusCode.WRONG_USERNAME_PASSWORD)

        if 'trade_pwd' in valid_params:
            if len(valid_params['trade_pwd']) < 6:
                raise err.ParamError('trade password too short')
            valid_params['trade_pwd'] = db.encode_password(
                valid_params['trade_pwd'])
            if user.trade_pwd is not None:
                check_params(params, ['old_trade_pwd'])
                if db.encode_password(
                        params['old_trade_pwd']) != user.trade_pwd:
                    raise err.AuthenticateError(
                        status=StatusCode.WRONG_USERNAME_PASSWORD)

        if 'citizen_id' in valid_params or 'name' in valid_params:
            if user.status & ACCOUNT_STATUS.NAMED:
                raise err.ParamError('account named already')
            valid = 'name' in valid_params and 'citizen_id' in valid_params
            if not valid:
                raise err.ParamError('name and citizen_id all needed')

        if 'avatar' in valid_params:
            valid_params['avatar'] = get_fixed_bucket(params['avatar'],
                                                      bucket='avatar',
                                                      disable_domain=True)

        db.update_account(req.user_id, valid_params)

        if 'password' in valid_params:
            db.logout_user(req.user_id)

        return {}

    @method_decorator(token_required)
    def delete(self, req, *args, **kwargs):
        raise NotImplementedError()

    @method_decorator(response_wrapper)
    def dispatch(self, *args, **kwargs):
        return super(UserView, self).dispatch(*args, **kwargs)


@require_POST
@response_wrapper
def reset_passwd(req):
    params = json.loads(req.body)
    check_params(params, ('phone', 'auth_code', 'password'))
    params['phone'] = get_fixed_num(params['phone'])
    if not check_auth_code(params['phone'], params['auth_code']):
        raise err.AuthenticateError(status=StatusCode.WRONG_AUTH_CODE)
    else:
        user_id = db.reset_passwd(
            params['phone'], params['password'])
        db.logout_user(user_id)
        delete_auth_code(params['phone'])
    return {}


@require_POST
@response_wrapper
@token_required
def reset_trade_pwd(req):
    params = json.loads(req.body)
    check_params(params, ('auth_code', 'trade_pwd'))
    if not check_auth_code(req.user.phone, params['auth_code']):
        raise err.AuthenticateError(status=StatusCode.WRONG_AUTH_CODE)
    else:
        db.update_account(
            req.user_id, {
                'trade_pwd': db.encode_password(params['trade_pwd'])})
        delete_auth_code(req.user.phone)
    return {}


@require_POST
@response_wrapper
@token_required
def check_trade_pwd(req):
    params = json.loads(req.body)
    check_params(params, ['trade_pwd'])
    if db.check_trade_pwd(req.user_id, params['trade_pwd']):
        return {}
    else:
        raise err.AuthenticateError(status=StatusCode.WRONG_USERNAME_PASSWORD)


@require_GET
@response_wrapper
def get_banks(req):
    return db.get_banks()


@require_POST
@response_wrapper
@token_required
def bind_financial_account(req):
    params = json.loads(req.body)
    check_params(params, ('auth_code', 'account', 'type', 'bank', 'sub_bank', 'real_name'))
    if params['type'] not in FinancialAccountType.value_list:
        raise err.ParamError('wrong type parameter')
    if not check_auth_code(req.user.phone, params['auth_code']):
        raise err.AuthenticateError(status=StatusCode.WRONG_AUTH_CODE)
    account_params = []
    for account_type in [WITHDRAW_BLACK_TYPE.BLACK_BANK, WITHDRAW_BLACK_TYPE.BLACK_ALIPAYNO,
                         WITHDRAW_BLACK_TYPE.BLACK_WECHAT, WITHDRAW_BLACK_TYPE.BAD_BANK]:
        account_params += list_type_account(account_type)
    if params['bank'] in account_params:
        raise err.ParamError('bank number in withdraw black')

    if get_financial_type_account_by_num(params['account'], params['type']):
        raise err.ParamError(u'该账号已被其他用户绑定')

    set_financial_account(req.user_id, params['account'], params['type'], params['bank'], params['sub_bank'],
                          params['real_name'])

    # 判断是否为新版本且绑定银行卡来发放注册送红包
    tracks = parse_p(req.GET.get('p'))
    cvc = tracks.get('cvc', 0)
    if int(cvc) > _VERSION and check_bind(req.user_id, 'bankcard'):
        add_new_fresh_coupon(req.user_id)
    return {}
